//
// decl.cs: Declaration base class for structs, classes, enums and interfaces.
//
// Author: Miguel de Icaza (miguel@gnu.org)
//         Marek Safar (marek.safar@seznam.cz)
//
// Dual licensed under the terms of the MIT X11 or GNU GPL
//
// Copyright 2001 Ximian, Inc (http://www.ximian.com)
// Copyright 2004-2008 Novell, Inc
// Copyright 2011 Xamarin Inc
//
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Mono.CompilerServices.SymbolWriter;

using System.Xml;

#if STATIC
using IKVM.Reflection;
using IKVM.Reflection.Emit;
#else
using System.Reflection;
using System.Reflection.Emit;
#endif

namespace Teal.Compiler {

	//
	// Better name would be DottenName
	//
	[DebuggerDisplay ("{GetSignatureForError()}")]
	public class MemberName
	{
		public static readonly MemberName Null = new MemberName ("");

		public readonly string Name;
		public TypeParameters TypeParameters;
		public readonly FullNamedExpression ExplicitInterface;
		public readonly Location2 Location;

		public readonly MemberName Left;

		public MemberName (string name)
			: this (name, Location2.Null)
		{ }

		public MemberName (string name, Location2 loc)
			: this (null, name, loc)
		{ }

		public MemberName (string name, TypeParameters tparams, Location2 loc)
		{
			this.Name = name;
			this.Location = loc;

			this.TypeParameters = tparams;
		}

		public MemberName (string name, TypeParameters tparams, FullNamedExpression explicitInterface, Location2 loc)
			: this (name, tparams, loc)
		{
			this.ExplicitInterface = explicitInterface;
		}

		public MemberName (MemberName left, string name, Location2 loc)
		{
			this.Name = name;
			this.Location = loc;
			this.Left = left;
		}

		public MemberName (MemberName left, string name, FullNamedExpression explicitInterface, Location2 loc)
			: this (left, name, loc)
		{
			this.ExplicitInterface = explicitInterface;
		}

		public int Arity {
			get {
				return TypeParameters == null ? 0 : TypeParameters.Count;
			}
		}

		public bool IsGeneric {
			get {
				return TypeParameters != null;
			}
		}

		public string Basename {
			get {
				if (TypeParameters != null)
					return MakeName (Name, TypeParameters);
				return Name;
			}
		}

		public void CreateMetadataName (StringBuilder sb)
		{
			if (Left != null)
				Left.CreateMetadataName (sb);

			if (sb.Length != 0) {
				sb.Append (".");
			}

			sb.Append (Basename);
		}

		public string GetSignatureForDocumentation ()
		{
			var s = Basename;

			if (ExplicitInterface != null)
				s = ExplicitInterface.GetSignatureForError () + "." + s;

			if (Left == null)
				return s;

			return Left.GetSignatureForDocumentation () + "." + s;
		}

		public string GetSignatureForError ()
		{
			string s = TypeParameters == null ? null : "<" + TypeParameters.GetSignatureForError () + ">";
			s = Name + s;

			if (ExplicitInterface != null)
				s = ExplicitInterface.GetSignatureForError () + "." + s;

			if (Left == null)
				return s;

			return Left.GetSignatureForError () + "." + s;
		}

		public override bool Equals (object other)
		{
			return Equals (other as MemberName);
		}

		public bool Equals (MemberName other)
		{
			if (this == other)
				return true;
			if (other == null || Name != other.Name)
				return false;

			if ((TypeParameters != null) &&
			    (other.TypeParameters == null || TypeParameters.Count != other.TypeParameters.Count))
				return false;

			if ((TypeParameters == null) && (other.TypeParameters != null))
				return false;

			if (Left == null)
				return other.Left == null;

			return Left.Equals (other.Left);
		}

		public override int GetHashCode ()
		{
			int hash = Name.GetHashCode ();
			for (MemberName n = Left; n != null; n = n.Left)
				hash ^= n.Name.GetHashCode ();

			if (TypeParameters != null)
				hash ^= TypeParameters.Count << 5;

			return hash & 0x7FFFFFFF;
		}

		public static string MakeName (string name, TypeParameters args)
		{
			if (args == null)
				return name;

			return name + "`" + args.Count;
		}

	}

	/// <summary>
	///   Base representation for members.  This is used to keep track
	///   of Name, Location and Modifier flags, and handling Attributes.
	/// </summary>
	public abstract partial class MemberDefinition : IMemberContext, IMemberDefinition
	{
		[Flags]
		public enum Flags2 {
			Obsolete_Undetected = 1,		// Obsolete attribute has not been detected yet
			Obsolete = 1 << 1,			// Type has obsolete attribute
			
			CloseTypeCreated = 1 << 4,		// Tracks whether we have Closed the type
			HasCompliantAttribute_Undetected = 1 << 5,	// Presence of CLSCompliantAttribute has not been detected
			HasClsCompliantAttribute = 1 << 6,			// Type has CLSCompliantAttribute
			ClsCompliantAttributeFalse = 1 << 7,			// Member has CLSCompliant(false)
			Excluded_Undetected = 1 << 8,		// Conditional attribute has not been detected yet
			Excluded = 1 << 9,					// Method is conditional
			MethodOverloadsExist = 1 << 10,		// Test for duplication must be performed
			IsUsed = 1 << 11,
			IsAssigned = 1 << 12,				// Field is assigned
			HasExplicitLayout	= 1 << 13,
			PartialDefinitionExists	= 1 << 14,	// Set when corresponding partial method definition exists
			HasStructLayout	= 1 << 15,			// Has StructLayoutAttribute
			HasInstanceConstructor = 1 << 16,
			HasUserOperators = 1 << 17,
			CanBeReused = 1 << 18,
			InterfacesExpanded = 1 << 19
		}

		/// <summary>
		///   MemberCore flags at first detected then cached
		/// </summary>
        internal Flags2 caching_flags = Flags2.Obsolete_Undetected | Flags2.HasCompliantAttribute_Undetected | Flags2.Excluded_Undetected;

		public bool IsCompilerGenerated {
			get	{
				if ((modifiers & Modifiers.COMPILER_GENERATED) != 0)
					return true;

				return container != null && container.IsCompilerGenerated;
			}
		}

		public bool IsImported {
			get {
				return false;
			}
		}

		public virtual bool IsUsed {
			get {
				return (caching_flags & Flags2.IsUsed) != 0;
			}
		}

		public void SetIsUsed ()
		{
			caching_flags |= Flags2.IsUsed;
		}

		public void SetIsAssigned ()
		{
			caching_flags |= Flags2.IsAssigned;
		}

		/// <summary>
		/// Returns instance of ObsoleteAttribute for this MemberCore
		/// </summary>
		public virtual ObsoleteAttribute GetAttributeObsolete ()
		{
			if ((caching_flags & (Flags2.Obsolete_Undetected | Flags2.Obsolete)) == 0)
				return null;

			caching_flags &= ~Flags2.Obsolete_Undetected;

			if (attributes == null)
				return null;

			MemberAnnotation obsolete_attr = attributes.Search (containerModule.PredefinedAttributes.Obsolete);
			if (obsolete_attr == null)
				return null;

			caching_flags |= Flags2.Obsolete;

			ObsoleteAttribute obsolete = obsolete_attr.GetObsoleteAttribute ();
			if (obsolete == null)
				return null;

			return obsolete;
		}

		/// <summary>
		/// Checks for ObsoleteAttribute presence. It's used for testing of all non-types elements
		/// </summary>
		public virtual void CheckObsoleteness (Location2 loc)
		{
			ObsoleteAttribute oa = GetAttributeObsolete ();
			if (oa != null)
				AttributeTester.Report_ObsoleteMessage (oa, GetSignatureForError (), loc, Compiler.Report);
		}

		/// <summary>
		/// Returns true when MemberCore is exposed from assembly.
		/// </summary>
		public bool IsExposedFromAssembly ()
		{
			if ((ModFlags & (Modifiers.@public | Modifiers.@protected)) == 0)
				return this is NamespaceDefinition;
			
			var parentContainer2 = container.PartialContainer;
			while (parentContainer2 != null) {
				if ((parentContainer2.ModFlags & (Modifiers.@public | Modifiers.@protected)) == 0)
					return false;

				parentContainer2 = parentContainer2.container.PartialContainer;
			}

			return true;
		}

		/// <summary>
		/// Returns true when a member supports multiple overloads (methods, indexers, etc)
		/// </summary>
		public virtual bool EnableOverloadChecks (MemberDefinition overload)
		{
			return false;
		}

		public virtual string GetCallerMemberName ()
		{
			return MemberName.Name;
		}

		public virtual void GetCompletionStartingWith (string prefix, List<string> results)
		{
			container.GetCompletionStartingWith (prefix, results);
		}

		#region IMemberContext Members
         
		public virtual TypeReference CurrentType {
			get { return container.CurrentType; }
		}

		public MemberDefinition CurrentMemberDefinition {
			get { return this; }
		}

		public virtual TypeParameters CurrentTypeParameters {
			get { return null; }
		}

		public bool IsObsolete {
			get {
				if (GetAttributeObsolete () != null)
					return true;

				return container != null && container.IsObsolete;
			}
		}

		public bool IsStatic {
			get {
				return (ModFlags & Modifiers.@static) != 0;
			}
		}

		#endregion

        string IMemberDefinition.name {
            get { throw new NotImplementedException(); }
        }
    }

	//
	// Base member specification. A member specification contains
	// member details which can alter in the context (e.g. generic instances)
	//
    public abstract partial class MemberReference
	{
		//
		// Some flags can be copied directly from other member
		//
		protected const ResolveFlags SharedStateFlags =
			ResolveFlags.Obsolete | ResolveFlags.Obsolete_Undetected |
			ResolveFlags.MissingDependency | ResolveFlags.MissingDependency_Undetected |
			ResolveFlags.HasDynamicElement;

		protected IMemberDefinition definition2;
		public readonly MemberKind Kind;

        public MemberReference() {}

        protected MemberReference (MemberKind kind, TypeReference declaringType, IMemberDefinition definition, Modifiers modifiers)
             : this()
		{
			this.Kind = kind;
			this.containerType = declaringType;
			this.definition2 = definition;
			this.modifiers = modifiers;

			if (kind == MemberKind.MissingType)
				resolveFlags = ResolveFlags.MissingDependency;
			else
				resolveFlags = ResolveFlags.Obsolete_Undetected | ResolveFlags.MissingDependency_Undetected;
		}

		public IMemberDefinition MemberDefinition2 {
			get {
				return definition2;
			}
		}

		public bool IsAccessor {
			get {
				return (resolveFlags & ResolveFlags.IsAccessor) != 0;
			}
			set {
				resolveFlags = value ? resolveFlags | ResolveFlags.IsAccessor : resolveFlags & ~ResolveFlags.IsAccessor;
			}
		}

		//
		// Return true when this member is a generic in C# terms
		// A nested non-generic type of generic type will return false
		//
		public bool IsGeneric {
			get {
				return (resolveFlags & ResolveFlags.IsGeneric) != 0;
			}
			set {
				resolveFlags = value ? resolveFlags | ResolveFlags.IsGeneric : resolveFlags & ~ResolveFlags.IsGeneric;
			}
		}

		//
		// Returns true for imported members which are not compatible with C# language
		//
		public bool IsNotCSharpCompatible {
			get {
				return (resolveFlags & ResolveFlags.IsNotCSharpCompatible) != 0;
			}
			set {
				resolveFlags = value ? resolveFlags | ResolveFlags.IsNotCSharpCompatible : resolveFlags & ~ResolveFlags.IsNotCSharpCompatible;
			}
		}

        public virtual ObsoleteAttribute GetAttributeObsolete ()
		{
			if ((resolveFlags & (ResolveFlags.Obsolete | ResolveFlags.Obsolete_Undetected)) == 0)
				return null;

			resolveFlags &= ~ResolveFlags.Obsolete_Undetected;

			var oa = definition2.GetAttributeObsolete ();
			if (oa != null)
				resolveFlags |= ResolveFlags.Obsolete;

			return oa;
		}

		//
		// Returns a list of missing dependencies of this member. The list
		// will contain types only but it can have numerous values for members
		// like methods where both return type and all parameters are checked
		//
		public List<MissingTypeSpecReference> GetMissingDependencies ()
		{
			return GetMissingDependencies (this);
		}

		public List<MissingTypeSpecReference> GetMissingDependencies (MemberReference caller)
		{
			if ((resolveFlags & (ResolveFlags.MissingDependency | ResolveFlags.MissingDependency_Undetected)) == 0)
				return null;

			resolveFlags &= ~ResolveFlags.MissingDependency_Undetected;

			var imported = definition2 as ImportedDefinition;
			List<MissingTypeSpecReference> missing;
			if (imported != null) {
				missing = ResolveMissingDependencies (caller);
			} else if (this is ElementTypeSpec) {
				missing = ((ElementTypeSpec) this).Element.GetMissingDependencies (caller);
			} else {
				missing = null;
			}

			if (missing != null) {
				resolveFlags |= ResolveFlags.MissingDependency;
			}

			return missing;
		}

		public abstract List<MissingTypeSpecReference> ResolveMissingDependencies (MemberReference caller);

        public virtual string GetSignatureForError ()
		{
			string name;
            name = this.name;

			return containerType.GetSignatureForError () + "." + name;
		}

		//
		// Is this member accessible from invocation context
		//
		public bool IsAccessible (IMemberContext ctx)
		{
			var ma = modifiers & Modifiers.ACCESSIBILITY;
			if (ma == Modifiers.@public)
				return true;

			var ctype = ctx.CurrentType;

			if (ma == Modifiers.@private) {
				if (ctype == null || containerType == null)
					return false;
				//
				// It's only accessible to the current class or children
				//
				if (containerType.MemberDefinition2 == ctype.MemberDefinition2)
					return true;

				return TypeManager.IsNestedChildOf (ctype, containerType.MemberDefinition2);
			}

			//
			// Checks whether `ctype' is a subclass or nested child of `parentType'.
			//
			while (ctype != null) {
				if (TypeManager.IsFamilyAccessible (ctype, containerType))
					return true;

				// Handle nested types.
				ctype = ctype.containerType;	// TODO: Untested ???
			}

			return false;
		}

	}

	//
	// Member details which are same between all member
	// specifications
	//
	public interface IMemberDefinition
	{
		string name { get; }
		bool IsImported { get; }

		ObsoleteAttribute GetAttributeObsolete ();
		void SetIsAssigned ();
		void SetIsUsed ();
	}

	public interface IMethodDefinition : IMemberDefinition
	{
		MethodBase Metadata { get; }
	}

	public interface IParametersMember : IInterfaceMemberSpec
	{
		AParametersCollection Parameters { get; }
	}

	public interface IInterfaceMemberSpec
	{
		TypeReference MemberType2 { get; }
	}
}
